<template>
  <div class="root-container">
    <a-spin :spinning="loading">
      <div class="toolbar">
        <a-space>
          <a-button-group>
            <a-button :icon="h(View)" title="预览JSON" @click="handlePreviewXml" />
            <a-button :icon="h(FolderOpened)" title="导入BPMN文件" @click="handleImportXml"/>
            <a-button :icon="h(Save)" title="保存" @click="handleSaveXml" />
            <a-button :icon="h(SaveAs)" title="另存为新版本" @click="handleSaveAsXml" />
            <a-button :icon="h(Check)" title="校验" @click="handleValidBpmn" />
            <a-button :icon="h(RefreshLeft)" title="重置" @click="handleReset" />
            <a-button :icon="h(Download)" title="下载" @click="handleDownloadXml" />
            <a-button :icon="h(TopRight)" title="新窗口打开" :disabled="route.name === 'workflow-ver-design'" @click="handleOpenNew" />
          </a-button-group>
        </a-space>
      </div>
      <div style="width: 100%; height: calc(100% - 41px); position: relative">
        <div style="width: 100%; height: 100%;" ref="diagramRef"></div>
        <div style="position: absolute; top: 0; right: 0; height: 100%">
          <Collapsed v-model:open="propertiesPanelVisible" position="right" style="height: 100%">
            <div style="box-sizing: border-box; width: 600px; height: 100%; background-color: var(--ant-bg-color); border-left: 1px solid var(--ant-border-color);">
              <PropertyPanel/>
            </div>
          </Collapsed>
        </div>
      </div>
      <xml-editor v-model:open="previewVisible" :code="previewCode" @confirm="handleUpdateXml" />
      <div id="workflow-mask-panel"></div>
      <a-tooltip
        v-model:open="errTooltipVisible"
        placement="right"
        :getPopupContainer="() => errEl"
        trigger="manual"
        :autoAdjustOverflow="true"
      >
        <template #title>
          <span style="color: red; font-weight: bold" v-text="errText"></span>
        </template>
      </a-tooltip>
    </a-spin>
  </div>
</template>

<script setup>
import { h } from 'vue';
import {onMounted, provide, ref, shallowRef, watch, nextTick} from "vue";
import { message } from 'ant-design-vue';
import { modelApi } from "/@/api/business/flowable/model";
import BpmnModeler from "bpmn-js/lib/Modeler"
import 'bpmn-js/dist/assets/bpmn-js.css';
import 'bpmn-js/dist/assets/diagram-js.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import 'diagram-js-minimap/assets/diagram-js-minimap.css'
import miniMapModule from 'diagram-js-minimap'
import flowableDescriptor from "../../../../assets/flowable/descriptor.json"
import { useIcon } from "/@/components/common/util";
import CustomModdle from '/@/components/bpmn/moddle/CustomModdle'
import Collapsed from "/@/components/common/Collapsed.vue";
import PropertyPanel from '/@/components/bpmn/PropertyPanel.vue'
import {
  bpmnModelerKey,
  bpmnSelectedElemKey,
  modelingFieldKey,
  modelingPageKey,
  workflowVerKey,
  actReModelKey
} from "/@/config/app.keys";
import emitter from "../../../../event/mitt";
import XmlEditor from "/@/components/common/XmlEditor.vue";
import ElementRegistry from 'diagram-js/lib/core/ElementRegistry'
import { validate } from './bpmnlint'
import Canvas from "diagram-js/lib/core/Canvas";
import InitBPMNXml from '/@/assets/bpmn/init.bpmn20.xml?raw'
import { useRoute, useRouter } from "vue-router";
import GridLineModule from 'diagram-js-grid-bg'
import {useModelingPageApi} from "/@/service/modeling/page";
import {useModelingFieldApi} from "/@/service/modeling/field";
import {useVerApi} from "/@/service/workflow/ver";
import {useActReModelApi} from "/@/service/workflow/actremodel";
import {
  EyeOutlined,
  FolderOpenOutlined,
  SaveOutlined,
  CopyOutlined,
  CheckOutlined,
  RollbackOutlined,
  ExportOutlined,
  DownloadOutlined
} from '@ant-design/icons-vue';
import { SmartLoading } from '/@/components/framework/smart-loading';

const route = useRoute()
console.log('route', route, route.name)
const router = useRouter()

const SaveIcon = useIcon('ali_save')
const SaveAsIcon = useIcon('ali_saveas')
const View = EyeOutlined
const FolderOpened = FolderOpenOutlined
const Save = SaveOutlined
const SaveAs = CopyOutlined
const Check = CheckOutlined
const RefreshLeft = RollbackOutlined
const TopRight = ExportOutlined
const Download = DownloadOutlined

const props = defineProps({
  bpmnXml: {
    type: String,
    required: true
  },
  id: {
    type: String,
    required: true
  }
});

const propertiesPanelVisible = ref(false)
const loading = ref(false)
const {actReModel,findModel} = useActReModelApi(loading)
const { workflowVer, findVer, updateXml } = useVerApi(loading)
const { modelingFields, findModelingFields } = useModelingFieldApi(loading)
const { pageList, findModulePages } = useModelingPageApi(loading)

provide(workflowVerKey, workflowVer)
provide(modelingFieldKey, modelingFields)
provide(modelingPageKey, pageList)
provide(actReModelKey,actReModel)
const diagramRef = ref()
const fileInput = ref(null)

onMounted(() => {
  // 创建文件输入元素
  fileInput.value = document.createElement('input')
  fileInput.value.type = 'file'
  fileInput.value.accept = '.xml,.bpmn'
  fileInput.value.style.display = 'none'
  document.body.appendChild(fileInput.value)
  
  fileInput.value.onchange = (e) => {
    const file = e.target.files[0]
    if (!file) return
    
    const reader = new FileReader()
    reader.onload = (event) => {
      const xml = event.target.result
      importXML(xml)
    }
    reader.readAsText(file)
  }
  
  initDiagram()
})

const scale = ref(1)
const bpmnModeler = shallowRef()
provide(bpmnModelerKey, bpmnModeler)
const bpmnSelectedElem = shallowRef()

provide(bpmnSelectedElemKey, bpmnSelectedElem)

function initDiagram() {
  bpmnModeler.value = new BpmnModeler({
    container: diagramRef.value,
    keyboard: {
      bindTo: window
    },
    additionalModules: [
      {
        // 禁用滚轮滚动
        zoomScroll: ["value", ""],
      },
      miniMapModule,
      CustomModdle,
      GridLineModule,
    ],
    moddleExtensions: {
      flowable: flowableDescriptor
    },
    minimap: {
      open: true
    },
    readOnly:false
  })

  // loading.value = true
  console.log('props.id:'+props.id);
  findModel(props.id)
      .then(() => {
        return findModulePages({module: 'WORKFLOW', mkey: actReModel.value.modelKey});
      } )
      .then(() => {
        if(actReModel.value.bpmnXml) {
          return importXML(actReModel.value.bpmnXml);
        }else{
          const xml = InitBPMNXml
              .replace(/{{PROCESS_ID}}/g, props.id)
              .replace(/{{PROCESS_NAME}}/g, actReModel.value.description)
              .replace(/{{START_EVENT_ID}}/g, Math.random().toString(36).replace(/0\./g, ''))
          return importXML(xml);
        }
      })
      // .finally(() => loading.value = false)
}

watch(
    () => props.id,
    (newVal) => {
      if(newVal!='' && typeof(newVal)!="undefined") {
        console.log('props.id1:'+props.id);
        findModel(props.id)
            .then(() => console.debug('actReModel:'+JSON.stringify(actReModel)))
      }
    },
    {
      immediate: true,
    }
)

function handleImportXml() {
  fileInput.value.click()
}

async function importXML(xml) {
  try {
    if (!bpmnModeler.value) {
      return
    }
    const result = await bpmnModeler.value.importXML(xml)
    const canvas = bpmnModeler.value.get('canvas')
    if (!canvas) {
      return
    }
    canvas.zoom("fit-viewport", true);
    canvas.zoom(Math.ceil(scale.value / 100));

    const registry = bpmnModeler.value.get("elementRegistry")
    if (!registry) {
      return
    }

    bpmnSelectedElem.value = registry.find(it => it.type === 'bpmn:Process')
    bpmnModeler.value.on("selection.changed", e => {
      console.log('element select change', e);
      const selectionArray = e.newSelection
      bpmnSelectedElem.value = selectionArray?.length ? selectionArray[0] : registry.find(it => it.type === 'bpmn:Process')
      emitter.emit('bpmnSelectionChanged', { element: bpmnSelectedElem.value })
    })
    bpmnModeler.value.on("element.changed", e => {
      console.log('element change', e);
      emitter.emit('bpmnElementChanged', { element: e.element })
    })
  } catch (e) {
    console.error(e)
    message.error("导入失败: " + e.message)
  }
}

const previewCode = ref('')
const previewVisible = ref(false)

async function handlePreviewXml() {
  if (!bpmnModeler.value) {
    return
  }
  try {
    const { xml } = await bpmnModeler.value.saveXML({ format: true })
    console.log("xml = " + xml)
    // 先设置数据
    previewCode.value = xml
    // 等待数据更新完成
    await nextTick()
    // 再显示对话框
    previewVisible.value = true
  } catch (error) {
    message.error('获取XML数据失败：' + error.message)
  }
}

function handleUpdateXml(xml) {
  importXML(xml)
  previewVisible.value = false
}

async function handleSaveXml() {
  if (!bpmnModeler.value) {
    return
  }
  const { xml } = await bpmnModeler.value.saveXML({ format: false })
  let res = await modelApi.saveModel({
      modelId: props.id,
      bpmnXml: xml,
      newVersion: false
    })
  if(res.ok){
    message.success('保存成功!')
  }else{
    message.error(res.msg || '保存失败！')
  }
}

async function handleSaveAsXml() {
  if (!bpmnModeler.value) {
    return
  }
  const { xml } = await bpmnModeler.value.saveXML({ format: false })
  await modelApi.saveModel({
      modelId: props.id,
      bpmnXml: xml,
      newVersion: true
    })
}

const errTooltipVisible = ref(false)
const errEl = shallowRef()
const errText = ref('')

function validateBpmn() {
  if (!bpmnModeler.value) {
    return false
  }
  const canvas = bpmnModeler.value.get('canvas')
  const root = canvas.getRootElement()
  console.log('root type', root.constructor.name, root)

  const registry = bpmnModeler.value.get("elementRegistry")

  const err = validate(registry, root)
  if (err) {
    if (!err.element) {
      message.error(err.message)
      return false
    }
    errEl.value = document.querySelector(`g.djs-element[data-element-id=${err.element.id}] .djs-visual`)
    errText.value = err.message
    errTooltipVisible.value = true
    return false
  }
  return true
}

function handleValidBpmn() {
  const result = validateBpmn()
  if (result) {
    message.success('校验成功 bpmn文件正常')
  }
}

function handleReset() {
  if (!bpmnModeler.value || !workflowVer.value) {
    return
  }
  const registry = bpmnModeler.value.get('elementRegistry');
  const process = registry.get(workflowVer.value.key);
  if (!process) {
    return
  }
  const xml = InitBPMNXml
    .replace(/{{PROCESS_ID}}/g, process.id)
    .replace(/{{PROCESS_NAME}}/g, process.businessObject.name)
    .replace(/{{START_EVENT_ID}}/g, 'StartEvent_' + Math.random().toString(36).replace(/0\./g, ''))
  importXML(xml)
}

function handleOpenNew() {
  router.push({
    name: 'workflow-ver-design',
    params: props
  })
}

async function handleDownloadXml() {
  if (!bpmnModeler.value) {
    return
  }
  try {
    const { xml } = await bpmnModeler.value.saveXML({ format: true })
    // 创建Blob对象
    const blob = new Blob([xml], { type: 'application/xml' })
    // 创建下载链接
    const url = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    // 设置文件名
    link.download = `${actReModel.value.description || 'workflow'}.bpmn`
    // 触发下载
    document.body.appendChild(link)
    link.click()
    // 清理
    document.body.removeChild(link)
    window.URL.revokeObjectURL(url)
  } catch (error) {
    message.error('下载失败：' + error.message)
  }
}
</script>

<style scoped>
div {
  box-sizing: border-box;
}

.root-container {
  width: 100%;
  height: 100%;
  border: 1px solid var(--ant-border-color);
}

.toolbar {
  box-sizing: border-box;
  width: 100%;
  padding: 4px;
  background-color: var(--toolbar-bg-color);
  border-bottom: 1px solid var(--ant-border-color);
}

:deep(.djs-minimap) {
  top: initial;
  right: initial;
  left: 20px;
  bottom: 40px;
  background-color: var(--ant-bg-color);
}

:deep(.bjs-powered-by) {
  right: initial;
  left: 20px;
  width: 200px;
}

/*:deep(.layer-selectionOutline) {*/
/*  display: none;*/
/*}*/



/*:deep(g.djs-element.djs-shape g.djs-visual circle) {*/
/*  fill: var(--ant-bg-color) !important;*/
/*  stroke: var(--ant-border-color)!important;*/
/*}*/

/*:deep(g.djs-element.djs-shape g.djs-visual rect) {*/
/*  fill: var(--ant-bg-color) !important;*/
/*  stroke: var(--ant-border-color)!important;*/
/*}*/

/*:deep(g.djs-element.djs-shape g.djs-visual polygon) {*/
/*  fill: var(--ant-bg-color) !important;*/
/*  stroke: var(--ant-border-color)!important;*/
/*}*/

/*:deep(g.djs-element.djs-shape g.djs-visual polygon ~ path) {*/
/*  fill: var(--ant-bg-color) !important;*/
/*  stroke: var(--ant-border-color)!important;*/
/*}*/

/*:deep(.djs-context-pad .entry) {*/
/*  background-color: var(--ant-bg-color);*/
/*}*/

/*:deep(g.djs-element.djs-shape g.djs-visual text) {*/
/*  fill: var(--ant-text-color-regular) !important;*/
/*}*/

/*:deep(g.djs-element.djs-connection g.djs-visual path) {*/
/*  stroke: var(--ant-border-color) !important;*/
/*}*/


</style>
